/**
 * @file
 * @brief RTD：XWDS：UART
 * @author
 * + 隐星魂 (Roy Sun) <xwos@xwos.tech>
 * @copyright
 * + Copyright © 2015 xwos.tech, All Rights Reserved.
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "board/std.h"
#include <string.h>
#include <xwos/osal/time.h>
#include <xwos/osal/swt.h>
#include <xwos/osal/irq.h>
#include <xwos/osal/sync/sem.h>
#include <xwos/osal/sync/cond.h>
#include <xwos/osal/lock/spinlock.h>
#include <xwcd/ds/uart/controller.h>
#include "Lpuart_Uart_Ip.h"

struct rtdxwds_lpuart_driver_data {
        uint8 instance;
        LPUART_Type * regbase;
        const Lpuart_Uart_Ip_UserConfigType * cfg;
        struct {
                struct xwos_splk lock;
                struct xwos_cond completion;
                Lpuart_Uart_Ip_StatusType state;
                struct xwos_sem available;
                xwu8_t * eqbuf;
                xwsz_t eqbuf_size;
                xwds_uartc_eqcb_f eqcb;
        } tx;
        struct {
                struct xwos_swt swt;
        } rx;
};

static
xwer_t rtdxwds_lpuart_drv_probe(struct xwds_device * dev);

static
xwer_t rtdxwds_lpuart_drv_remove(struct xwds_device * dev);

static
xwer_t rtdxwds_lpuart_drv_start(struct xwds_device * dev);

static
xwer_t rtdxwds_lpuart_drv_stop(struct xwds_device * dev);

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t rtdxwds_lpuart_drv_resume(struct xwds_device * dev);

static
xwer_t rtdxwds_lpuart_drv_suspend(struct xwds_device * dev);
#endif

static
xwer_t rtdxwds_lpuart_drv_cfg(struct xwds_uartc * uartc,
                              const struct xwds_uart_cfg * cfg);

static
xwer_t rtdxwds_lpuart_drv_tx(struct xwds_uartc * uartc,
                             const xwu8_t * data, xwsz_t * size,
                             xwtm_t to);

static
xwer_t rtdxwds_lpuart_drv_eq(struct xwds_uartc * uartc,
                             const xwu8_t * data, xwsz_t * size,
                             xwds_uartc_eqcb_f cb);

static
xwer_t rtdxwds_lpuart_drv_putc(struct xwds_uartc * uartc,
                               const xwu8_t byte);

static
void rtdxwds_lpuart_swt_callback(struct xwos_swt * swt, void * arg);

const struct xwds_uartc_driver rtdxwds_lpuart_drv = {
        .base = {
                .name = "rtdxwds.lpuart",
                .probe = rtdxwds_lpuart_drv_probe,
                .remove = rtdxwds_lpuart_drv_remove,
                .start = rtdxwds_lpuart_drv_start,
                .stop = rtdxwds_lpuart_drv_stop,
#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
                .suspend = rtdxwds_lpuart_drv_suspend,
                .resume =  rtdxwds_lpuart_drv_resume,
#endif
        },
        .cfg = rtdxwds_lpuart_drv_cfg,
        .tx = rtdxwds_lpuart_drv_tx,
        .eq = rtdxwds_lpuart_drv_eq,
        .putc = rtdxwds_lpuart_drv_putc,
};

/******** ******** lpuart2 ******** ********/
__xwcc_alignl1cache xwu8_t rtdxwds_lpuart2_eqbuffer[XWLIBCFG_XWLOG_BUFSIZE] = {0};

struct rtdxwds_lpuart_driver_data rtdxwds_lpuart2_drvdata = {
        .instance = LPUART_UART_IP_INSTANCE_USING_2,
        .regbase = IP_LPUART2,
        .cfg = &Lpuart_Uart_Ip_xHwConfigPB_2_BOARD_INITPERIPHERALS,
        .tx = {
                .eqbuf = rtdxwds_lpuart2_eqbuffer,
                .eqbuf_size = sizeof(rtdxwds_lpuart2_eqbuffer),
        },
};

struct xwds_uartc rtdxwds_lpuart2 = {
        /* attributes */
        .dev = {
                .name = "rtdxwds.lpuart",
                .id = 2,
                .resources = NULL,
                .drv = xwds_cast(struct xwds_driver *, &rtdxwds_lpuart_drv),
                .data = (void *)&rtdxwds_lpuart2_drvdata,
        },
        .cfg = NULL,
};

/******** ******** devices ******** ********/
struct xwds_uartc * const rtdxwds_lpuart_devices[] = {
        [0] = NULL,
        [1] = NULL,
        [2] = &rtdxwds_lpuart2,
        [3] = NULL,
        [4] = NULL,
        [5] = NULL,
        [6] = NULL,
        [7] = NULL,
};

/******** ******** driver functions ******** ********/
static
xwer_t rtdxwds_lpuart_drv_probe(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct rtdxwds_lpuart_driver_data * drvdata;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        drvdata->tx.eqcb = NULL;
        xwos_splk_init(&drvdata->tx.lock);
        xwos_cond_init(&drvdata->tx.completion);
        xwos_sem_init(&drvdata->tx.available, 1, 1);
        Lpuart_Uart_Ip_Init(drvdata->instance, drvdata->cfg);
        return XWOK;
}

static
xwer_t rtdxwds_lpuart_drv_remove(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct rtdxwds_lpuart_driver_data * drvdata;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        Lpuart_Uart_Ip_Deinit(drvdata->instance);
        xwos_sem_fini(&drvdata->tx.available);
        xwos_cond_fini(&drvdata->tx.completion);
        return XWOK;
}

static
xwer_t rtdxwds_lpuart_drv_start(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct rtdxwds_lpuart_driver_data * drvdata;
        Lpuart_Uart_Ip_StatusType lpuart_state;
        xwer_t rc;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        xwos_swt_init(&drvdata->rx.swt, XWOS_SWT_FLAG_RESTART);
        xwds_uartc_drvcb_rxq_flush(uartc);
        lpuart_state = Lpuart_Uart_Ip_AsyncReceive(drvdata->instance,
                                                   uartc->rxq.mem,
                                                   sizeof(uartc->rxq.mem));
        if (LPUART_UART_IP_STATUS_SUCCESS == lpuart_state) {
                rc = xwos_swt_start(&drvdata->rx.swt, xwtm_now(), xwtm_ms(10),
                                    rtdxwds_lpuart_swt_callback, uartc);
                if (rc < 0) {
                        Lpuart_Uart_Ip_AbortReceivingData(drvdata->instance);
                }
        } else {
                rc = -EBUSY;
        }
        return rc;
}

static
xwer_t rtdxwds_lpuart_drv_stop(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct rtdxwds_lpuart_driver_data * drvdata;
        xwreg_t cpuirq;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        Lpuart_Uart_Ip_AbortSendingData(drvdata->instance);
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        drvdata->tx.state = Lpuart_Uart_Ip_GetTransmitStatus(drvdata->instance,
                                                             NULL_PTR);
        xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
        xwos_cond_broadcast(&drvdata->tx.completion);

        xwos_swt_stop(&drvdata->rx.swt);
        Lpuart_Uart_Ip_AbortReceivingData(drvdata->instance);
        return XWOK;
}

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t rtdxwds_lpuart_drv_resume(struct xwds_device * dev)
{
        return rtdxwds_lpuart_drv_start(dev);
}

static
xwer_t rtdxwds_lpuart_drv_suspend(struct xwds_device * dev)
{
        return rtdxwds_lpuart_drv_stop(dev);
}
#endif

static
xwer_t rtdxwds_lpuart_drv_cfg(struct xwds_uartc * uartc,
                              const struct xwds_uart_cfg * cfg)
{
        XWOS_UNUSED(uartc);
        XWOS_UNUSED(cfg);
        return -ENOSYS;
}

static
xwer_t rtdxwds_lpuart_drv_tx(struct xwds_uartc * uartc,
                             const xwu8_t * data, xwsz_t * size,
                             xwtm_t to)
{
        struct rtdxwds_lpuart_driver_data * drvdata;
        Lpuart_Uart_Ip_StatusType lpuart_state;
        xwsz_t wrsz;
        union xwos_ulock lock;
        xwsq_t lkst;
        xwreg_t cpuirq;
        xwer_t rc;

        drvdata = uartc->dev.data;
        rc = xwos_sem_wait_to(&drvdata->tx.available, to);
        if (rc < 0) {
                goto err_sem_wait_to;
        }
        lock.osal.splk = &drvdata->tx.lock;
        wrsz = *size;
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        lpuart_state = Lpuart_Uart_Ip_AsyncSend(drvdata->instance,
                                                data,
                                                wrsz);
        if (LPUART_UART_IP_STATUS_SUCCESS == lpuart_state) {
                drvdata->tx.state = Lpuart_Uart_Ip_GetTransmitStatus(drvdata->instance,
                                                                     NULL_PTR);
                rc = xwos_cond_wait_to(&drvdata->tx.completion, lock, XWOS_LK_SPLK,
                                       NULL, to, &lkst);
                if (XWOK == rc) {
                        XWOS_BUG_ON((xwsq_t)XWOS_LKST_UNLOCKED == lkst);
                        if (LPUART_UART_IP_STATUS_SUCCESS == drvdata->tx.state) {
                                rc = XWOK;
                        } else if (LPUART_UART_IP_STATUS_ABORTED == drvdata->tx.state) {
                                rc = -ECANCELED;
                        } else {
                                rc =-EIO;
                        }
                        xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                } else {
                        if ((xwsq_t)XWOS_LKST_LOCKED == lkst) {
                                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                        } else {
                                xwos_cpuirq_restore_lc(cpuirq);
                        }
                }
        } else {
                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                xwos_sem_post(&drvdata->tx.available);
                rc = -EIO;
                *size = 0;
        }

err_sem_wait_to:
        return rc;
}

static
xwer_t rtdxwds_lpuart_drv_eq(struct xwds_uartc * uartc,
                             const xwu8_t * data, xwsz_t * size,
                             xwds_uartc_eqcb_f cb)
{
        struct rtdxwds_lpuart_driver_data * drvdata;
        Lpuart_Uart_Ip_StatusType rtdrc;
        xwsz_t wrsz;
        xwreg_t cpuirq;
        xwer_t rc;

        drvdata = uartc->dev.data;
        if ((NULL == drvdata->tx.eqbuf) || (drvdata->tx.eqbuf_size == 0)) {
                rc = -ENOSYS;
                goto err_nosys;
        }
        rc = xwos_sem_trywait(&drvdata->tx.available);
        if (rc < 0) {
                rc = -EBUSY;
                goto err_busy;
        }

        wrsz = *size > drvdata->tx.eqbuf_size ? drvdata->tx.eqbuf_size : *size;
        memcpy(drvdata->tx.eqbuf, data, wrsz);
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        drvdata->tx.eqcb = cb;
        rtdrc = Lpuart_Uart_Ip_AsyncSend(drvdata->instance, drvdata->tx.eqbuf, wrsz);
        if (LPUART_UART_IP_STATUS_SUCCESS == rtdrc) {
                drvdata->tx.state = Lpuart_Uart_Ip_GetTransmitStatus(drvdata->instance,
                                                                     NULL_PTR);
                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                *size = wrsz;
                rc = XWOK;
        } else {
                drvdata->tx.eqcb = NULL;
                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                xwos_sem_post(&drvdata->tx.available);
                rc = -EIO;
                *size = 0;
        }

err_busy:
err_nosys:
        return rc;
}

static
xwer_t rtdxwds_lpuart_drv_putc(struct xwds_uartc * uartc,
                               const xwu8_t byte)
{
        struct rtdxwds_lpuart_driver_data * drvdata;

        drvdata = uartc->dev.data;
        Lpuart_Uart_Ip_Putchar(drvdata->regbase, byte);
        return XWOK;
}

/******** ******** callbacks ******** ********/
void rtdxwds_lpuart_txcb_complete(struct xwds_uartc * uartc,
                                  const uint8 instance)
{
        struct rtdxwds_lpuart_driver_data * drvdata;
        xwreg_t cpuirq;
        xwds_uartc_eqcb_f cb = NULL;
        xwer_t cbrc;

        drvdata = uartc->dev.data;
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        drvdata->tx.state = Lpuart_Uart_Ip_GetTransmitStatus(instance, NULL_PTR);
        switch (drvdata->tx.state) {
        case LPUART_UART_IP_STATUS_SUCCESS:
                cbrc = XWOK;
                break;
        case LPUART_UART_IP_STATUS_ABORTED:
                cbrc = -ECANCELED;
                break;
        default:
                cbrc = -EIO;
                break;
        }
        cb = drvdata->tx.eqcb;
        drvdata->tx.eqcb = NULL;
        xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
        xwos_cond_broadcast(&drvdata->tx.completion);
        if (NULL != cb) {
                cb(uartc, cbrc);
        }
        xwos_sem_post(&drvdata->tx.available);
}

void rtdxwds_lpuart_rxcb_complete(struct xwds_uartc * uartc,
                                  const uint8 instance)
{
        xwds_uartc_drvcb_rxq_pub(uartc, 0);
        Lpuart_Uart_Ip_SetRxBuffer(instance, uartc->rxq.mem, sizeof(uartc->rxq.mem));
}

void rtdxwds_lpuart_rxcb_recirculating(struct xwds_uartc * uartc,
                                       const uint8 instance)
{
        XWOS_UNUSED(instance);
        xwds_uartc_drvcb_rxq_pub(uartc, 0);
}

void rtdxwds_lpuart_rxcb_idle(struct xwds_uartc * uartc,
                              const uint8 instance)
{
        xwu32_t rest;
        xwsq_t tail;
        xwreg_t cpuirq;

        xwos_cpuirq_save_lc(&cpuirq);
        Lpuart_Uart_Ip_GetReceiveStatus(instance, &rest);
        if (rest > 0) {
                tail = sizeof(uartc->rxq.mem) - rest;
                xwds_uartc_drvcb_rxq_pub(uartc, tail);
        }
        xwos_cpuirq_restore_lc(cpuirq);
}

void rtdxwds_lpuart_rxcb_end(struct xwds_uartc * uartc,
                             const uint8 instance)
{
        Lpuart_Uart_Ip_StatusType lpuart_state;

        lpuart_state = Lpuart_Uart_Ip_GetReceiveStatus(instance, NULL_PTR);
        switch (lpuart_state) {
        case LPUART_UART_IP_STATUS_ERROR:
        case LPUART_UART_IP_STATUS_RX_OVERRUN:
        case LPUART_UART_IP_STATUS_FRAMING_ERROR:
        case LPUART_UART_IP_STATUS_PARITY_ERROR:
        case LPUART_UART_IP_STATUS_NOISE_ERROR:
        case LPUART_UART_IP_STATUS_DMA_ERROR:
                xwds_uartc_drvcb_rxq_flush(uartc);
                Lpuart_Uart_Ip_AsyncReceive(instance,
                                            &uartc->rxq.mem[0],
                                            sizeof(uartc->rxq.mem));
                break;
        case LPUART_UART_IP_STATUS_SUCCESS:
        case LPUART_UART_IP_STATUS_BUSY:
        case LPUART_UART_IP_STATUS_TIMEOUT:
        case LPUART_UART_IP_STATUS_ABORTED:
                break;
        default:
                break;
        }

}

void rtdxwds_lpuart_callback(const uint8 instance,
                             const Lpuart_Uart_Ip_EventType event, void * data)
{
        struct xwds_uartc * uartc;

        XWOS_UNUSED(data);
        uartc = rtdxwds_lpuart_devices[instance];
        switch (event) {
        case LPUART_UART_IP_EVENT_RX_FULL:
                rtdxwds_lpuart_rxcb_complete(uartc, instance);
                break;
        case LPUART_UART_IP_EVENT_RX_END:
                rtdxwds_lpuart_rxcb_end(uartc, instance);
                break;
        case LPUART_UART_IP_EVENT_RX_ERROR:
                break;
        case LPUART_UART_IP_EVENT_RX_RECIRCULATING:
                rtdxwds_lpuart_rxcb_recirculating(uartc, instance);
                break;
        case LPUART_UART_IP_EVENT_TX_EMPTY:
                break;
        case LPUART_UART_IP_EVENT_TX_END:
                rtdxwds_lpuart_txcb_complete(uartc, instance);
                break;
        case LPUART_UART_IP_EVENT_TX_ERROR:
                break;
        default:
                break;
        }
}

static
void rtdxwds_lpuart_swt_callback(struct xwos_swt * swt, void * arg)
{
        struct xwds_uartc * uartc;
        struct rtdxwds_lpuart_driver_data * drvdata;

        XWOS_UNUSED(swt);
        uartc = arg;
        drvdata = uartc->dev.data;
        rtdxwds_lpuart_rxcb_idle(uartc, drvdata->instance);
}
